"""
Base class for Level Editor

You should write your own LevelEditor class inheriting this.
Refer LevelEditor.py for example.
"""

import os
import wx

from direct.showbase.DirectObject import DirectObject
from direct.directtools.DirectUtil import useDirectRenderStyle
from direct.gui.OnscreenText import OnscreenText
from panda3d.core import BitMask32, ClockObject, Mat4, NodePath, Point3, TextNode, Vec4

from . import ObjectGlobals as OG
from .CurveEditor import CurveEditor
from .FileMgr import FileMgr
from .ActionMgr import (
    ActionDeleteObj,
    ActionDeselectAll,
    ActionMgr,
    ActionSelectObj,
    ActionTransformObj,
)
from .MayaConverter import FROM_BAM_TO_MAYA, MayaConverter


class LevelEditorBase(DirectObject):
    """ Base Class for Panda3D LevelEditor """

    def __init__(self):
        #loadPrcFileData('startup', 'window-type none')
        self.currentFile = None
        self.fNeedToSave = False
        self.actionEvents = []
        #self.objectMgr = ObjectMgr(self)
        self.curveEditor = CurveEditor(self)
        self.fileMgr = FileMgr(self)
        self.actionMgr = ActionMgr()

        self.fMoveCamera = False

        self.NPParent = base.render

        # define your own config file in inherited class
        self.settingsFile = None

        # you can show/hide specific properties by using propertiesMask and this mode
        self.BASE_MODE = BitMask32.bit(0)
        self.CREATE_CURVE_MODE = BitMask32.bit(2)
        self.EDIT_CURVE_MODE = BitMask32.bit(3)
        self.ANIM_MODE = BitMask32.bit(4)
        self.GRAPH_EDITOR = False

        self.mode = self.BASE_MODE
        self.preMode = None

    def initialize(self):
        """ You should call this in your __init__ method of inherited LevelEditor class """
        # specifiy what obj can be 'selected' as objects
        base.direct.selected.addTag('OBJRoot')

        self.actionEvents.extend([
            # Node path events
            ('DIRECT-select', self.select),
            ('DIRECT-delete', self.handleDelete),
            ('DIRECT-preDeselectAll', self.deselectAll),
            ('DIRECT_deselectAll', self.deselectAllCB),
            ('preRemoveNodePath', self.removeNodePathHook),
            ('DIRECT_deselectedNodePath', self.deselectAllCB),
            ('DIRECT_selectedNodePath_fMulti_fTag_fLEPane', self.selectedNodePathHook),
            ('DIRECT_deselectAll', self.deselectAll),
            ('LE-Undo', self.actionMgr.undo),
            ('LE-Redo', self.actionMgr.redo),
            ('LE-Duplicate', self.objectMgr.duplicateSelected),
            ('DIRECT_manipulateObjectCleanup', self.cleanUpManipulating),
            ('LE-MakeLive', self.objectMgr.makeSelectedLive),
            ('LE-NewScene', self.ui.onNew),
            ('LE-SaveScene', self.ui.onSave),
            ('LE-OpenScene', self.ui.onOpen),
            ('LE-Quit', self.ui.quit),
            ('DIRECT-mouse1', self.handleMouse1),
            ('DIRECT-mouse1Up', self.handleMouse1Up),
            ('DIRECT-mouse2', self.handleMouse2),
            ('DIRECT-mouse2Up', self.handleMouse2Up),
            ('DIRECT-mouse3', self.handleMouse3),
            ('DIRECT-mouse3Up', self.handleMouse3Up),
            ('DIRECT-toggleWidgetVis', self.toggleWidget),
        ])

        # Add all the action events
        for event in self.actionEvents:
            if len(event) == 3:
                self.accept(event[0], event[1], event[2])
            else:
                self.accept(event[0], event[1])

        # editor state text display such as edit mode
        self.statusReadout = OnscreenText(
            pos = (-1.2, 0.9), bg=Vec4(1,1,1,1),
            scale = 0.05, align = TextNode.ALeft,
            mayChange = 1, font = TextNode.getDefaultFont())
        self.statusReadout.setText("")
        # Make sure readout is never lit or drawn in wireframe
        useDirectRenderStyle(self.statusReadout)
        self.statusReadout.reparentTo(hidden)
        self.statusLines = []
        base.taskMgr.doMethodLater(5, self.updateStatusReadoutTimeouts, 'updateStatus')

        self.loadSettings()
        self.reset()

    def setTitleWithFilename(self, filename=""):
        title = self.ui.appname
        if filename != "":
            filenameshort = os.path.basename(filename)
            title = title + " (%s)"%filenameshort
        self.ui.SetLabel(title)

    def removeNodePathHook(self, nodePath):
        if nodePath is None:
            return
        base.direct.deselect(nodePath)
        self.objectMgr.removeObjectByNodePath(nodePath)

        if base.direct.selected.last is not None and nodePath == base.direct.selected.last:
            # if base.direct.selected.last is refering to this
            # removed obj, clear the reference
            if hasattr(__builtins__, 'last'):
                __builtins__.last = None
            else:
                __builtins__['last'] = None
            base.direct.selected.last = None

    def toggleWidget(self):
        if self.objectMgr.currNodePath:
            obj = self.objectMgr.findObjectByNodePath(self.objectMgr.currNodePath)
            if obj and not obj[OG.OBJ_DEF].movable:
                return
        base.direct.toggleWidgetVis()

    def handleMouse1(self, modifiers):
        if base.direct.fAlt or modifiers == 4:
            self.fMoveCamera = True
            return
        if self.mode == self.CREATE_CURVE_MODE:
            self.curveEditor.createCurve()

    def handleMouse1Up(self):
        self.fMoveCamera = False

    def handleMouse2(self, modifiers):
        if base.direct.fAlt or modifiers == 4:
            self.fMoveCamera = True
            return

    def handleMouse2Up(self):
        self.fMoveCamera = False

    def handleMouse3(self, modifiers):
        if base.direct.fAlt or modifiers == 4:
            self.fMoveCamera = True
            return

        self.ui.onRightDown()

    def handleMouse3Up(self):
        self.fMoveCamera = False

    def handleDelete(self):
        oldSelectedNPs = base.direct.selected.getSelectedAsList()
        oldUIDs = []
        for oldNP in oldSelectedNPs:
            obj = self.objectMgr.findObjectByNodePath(oldNP)
            if obj:
                oldUIDs.append(obj[OG.OBJ_UID])

        action = ActionDeleteObj(self)
        self.actionMgr.push(action)
        action()

        for uid in oldUIDs:
            self.ui.sceneGraphUI.delete(uid)

##         reply = wx.MessageBox("Do you want to delete selected?", "Delete?",
##                               wx.YES_NO | wx.ICON_QUESTION)
##         if reply == wx.YES:
##             base.direct.removeAllSelected()
##         else:
##             # need to reset COA
##             dnp = base.direct.selected.last
##             # Update camera controls coa to this point
##             # Coa2Camera = Coa2Dnp * Dnp2Camera
##             mCoa2Camera = dnp.mCoa2Dnp * dnp.getMat(base.direct.camera)
##             row = mCoa2Camera.getRow(3)
##             coa = Vec3(row[0], row[1], row[2])
##             base.direct.cameraControl.updateCoa(coa)

    def cleanUpManipulating(self, selectedNPs):
        for np in selectedNPs:
            obj = self.objectMgr.findObjectByNodePath(np)
            if obj:
                action = ActionTransformObj(self, obj[OG.OBJ_UID], Mat4(np.getMat()))
                self.actionMgr.push(action)
                action()

    def select(self, nodePath, fMultiSelect=0, fSelectTag=1, fResetAncestry=1, fLEPane=0, fUndo=1):
        if fUndo:
            # Select tagged object if present
            if fSelectTag:
                for tag in base.direct.selected.tagList:
                    if nodePath.hasNetTag(tag):
                        nodePath = nodePath.findNetTag(tag)
                        break
            action = ActionSelectObj(self, nodePath, fMultiSelect)
            self.actionMgr.push(action)
            action()
        else:
            base.direct.selectCB(nodePath, fMultiSelect, fSelectTag, fResetAncestry, fLEPane, fUndo)

    def selectedNodePathHook(self, nodePath, fMultiSelect = 0, fSelectTag = 1, fLEPane = 0):
        # handle unpickable nodepath
        if nodePath.getName() in base.direct.iRay.unpickable:
            base.direct.deselect(nodePath)
            return

        if fMultiSelect == 0 and fLEPane == 0:
            oldSelectedNPs = base.direct.selected.getSelectedAsList()
            for oldNP in oldSelectedNPs:
                obj = self.objectMgr.findObjectByNodePath(oldNP)
                if obj:
                    self.ui.sceneGraphUI.deSelect(obj[OG.OBJ_UID])
        self.objectMgr.selectObject(nodePath, fLEPane)
        self.ui.buildContextMenu(nodePath)

        if self.mode == self.EDIT_CURVE_MODE:
            base.taskMgr.add(self.curveEditor.editCurve, "modify")
            self.curveEditor.accept("DIRECT-enter", self.curveEditor.onBaseMode)

    def deselectAll(self, np=None):
        if len(base.direct.selected.getSelectedAsList()) ==0:
            return
        action = ActionDeselectAll(self)
        self.actionMgr.push(action)
        action()

    def deselectAllCB(self, dnp=None):
        self.objectMgr.deselectAll()

    def reset(self):
        if self.fNeedToSave:
            reply = wx.MessageBox("Do you want to save current scene?", "Save?",
                               wx.YES_NO | wx.ICON_QUESTION)
            if reply == wx.YES:
                if not self.ui.onSave():
                    return

        base.direct.deselectAll()
        base.direct.selected.last = None
        self.ui.reset()
        self.objectMgr.reset()
        self.animMgr.reset()
        self.actionMgr.reset()
        self.ui.perspView.camera.setPos(-19, -19, 19)
        self.ui.perspView.camera.lookAt(Point3(0, 0, 0))
        self.ui.leftView.camera.setPos(600, 0, 0)
        self.ui.frontView.camera.setPos(0, -600, 0)
        self.ui.topView.camera.setPos(0, 0, 600)
        self.resetOrthoCam(self.ui.topView)
        self.resetOrthoCam(self.ui.frontView)
        self.resetOrthoCam(self.ui.leftView)
        self.fNeedToSave = False
        self.setTitleWithFilename()

    def resetOrthoCam(self, view):
        base.direct.drList[base.camList.index(NodePath(view.camNode))].orthoFactor = 0.1
        x = view.ClientSize.GetWidth() * 0.1
        y = view.ClientSize.GetHeight() * 0.1
        view.camLens.setFilmSize(x, y)

    def save(self):
        self.ui.SetCursor(wx.StockCursor(wx.CURSOR_WAIT))
        if self.currentFile:
            self.fileMgr.saveToFile(self.currentFile)
        self.ui.SetCursor(wx.StockCursor(wx.CURSOR_ARROW))

    def saveAs(self, fileName):
        self.ui.SetCursor(wx.StockCursor(wx.CURSOR_WAIT))
        self.fileMgr.saveToFile(fileName)
        self.currentFile = fileName
        self.ui.SetCursor(wx.StockCursor(wx.CURSOR_ARROW))

    def load(self, fileName):
        self.ui.SetCursor(wx.StockCursor(wx.CURSOR_WAIT))
        self.reset()
        self.fileMgr.loadFromFile(fileName)
        self.currentFile = fileName
        self.ui.SetCursor(wx.StockCursor(wx.CURSOR_ARROW))

    def saveSettings(self):
        if self.settingsFile is None:
            return

        try:
            f = open(self.settingsFile, 'w')
            f.write('gridSize\n%f\n'%self.ui.perspView.grid.gridSize)
            f.write('gridSpacing\n%f\n'%self.ui.perspView.grid.gridSpacing)
            f.write('hotKey\n%s\n'%base.direct.hotKeyMap)
            f.close()
        except:
            pass

    def loadSettings(self):
        if self.settingsFile is None:
            return

        self.ui.SetCursor(wx.Cursor(wx.CURSOR_WAIT))
        try:
            f = open(self.settingsFile, 'r')
            configLines = f.readlines()
            f.close()

            gridSize = 100.0
            gridSpacing = 5.0
            for i in range(0, len(configLines)):
                line = configLines[i]
                i = i + 1
                if line.startswith('gridSize'):
                    gridSize = float(configLines[i])
                elif line.startswith('gridSpacing'):
                    gridSpacing = float(configLines[i])
                elif line.startswith('hotKey'):
                    customHotKeyMap = eval(configLines[i])
                    customHotKeyDict = {}
                    for hotKey in customHotKeyMap.keys():
                        desc = customHotKeyMap[hotKey]
                        customHotKeyDict[desc[1]] = hotKey

                    overriddenKeys = []
                    for key in base.direct.hotKeyMap.keys():
                        desc = base.direct.hotKeyMap[key]
                        if desc[1] in customHotKeyDict.keys():
                            overriddenKeys.append(key)

                    for key in overriddenKeys:
                        del base.direct.hotKeyMap[key]

                    base.direct.hotKeyMap.update(customHotKeyMap)

            self.ui.updateGrids(gridSize, gridSpacing)
            self.ui.updateMenu()
        except:
            pass
        self.ui.SetCursor(wx.Cursor(wx.CURSOR_ARROW))

    def convertMaya(self, modelname, callBack, obj=None, isAnim=False):
        if obj and isAnim:
            mayaConverter = MayaConverter(self.ui, self, modelname, callBack, obj, isAnim)
        else:
            reply = wx.MessageBox("Is it an animation file?", "Animation?",
                              wx.YES_NO | wx.ICON_QUESTION)
            if reply == wx.YES:
                mayaConverter = MayaConverter(self.ui, self, modelname, callBack, None, True)
            else:
                mayaConverter = MayaConverter(self.ui, self, modelname, callBack, None, False)
        mayaConverter.Show()

    def convertFromMaya(self, modelname, callBack):
        mayaConverter = MayaConverter(self.ui, self, modelname, callBack, None, False)
        mayaConverter.Show()

    def exportToMaya(self, mayaFileName):
        exportRootNP = render
        self.exportToMayaCB(mayaFileName, exportRootNP)

    def exportToMayaCB(self, mayaFileName, exportRootNP):
        bamFileName = mayaFileName + ".bam"

        if base.direct.selected.last:
            obj = self.objectMgr.findObjectByNodePath(base.direct.selected.last)
            if obj:
                exportRootNP = obj[OG.OBJ_NP]

        exportRootNP.writeBamFile(bamFileName)
        mayaConverter = MayaConverter(self.ui, self, mayaFileName, None, None, False, FROM_BAM_TO_MAYA)
        mayaConverter.Show()

    def updateStatusReadout(self, status, color=None):
        if status:
            # add new status line, first check to see if it already exists
            alreadyExists = False
            for currLine in self.statusLines:
                if status == currLine[1]:
                    alreadyExists = True
                    break
            if not alreadyExists:
                time = ClockObject.getGlobalClock().getRealTime() + 15
                self.statusLines.append([time,status,color])

        # update display of new status lines
        self.statusReadout.reparentTo(base.aspect2d)
        statusText = ""
        lastColor = None
        for currLine in self.statusLines:
            statusText += currLine[1] + '\n'
            lastColor = currLine[2]
        self.statusReadout.setText(statusText)
        if lastColor:
            self.statusReadout.textNode.setCardColor(
                lastColor[0], lastColor[1], lastColor[2], lastColor[3])
            self.statusReadout.textNode.setCardAsMargin(0.1, 0.1, 0.1, 0.1)
        else:
            self.statusReadout.textNode.setCardColor(1,1,1,1)
            self.statusReadout.textNode.setCardAsMargin(0.1, 0.1, 0.1, 0.1)

    def updateStatusReadoutTimeouts(self,task=None):
        removalList = []
        for currLine in self.statusLines:
            if ClockObject.getGlobalClock().getRealTime() >= currLine[0]:
                removalList.append(currLine)
        for currRemoval in removalList:
            self.statusLines.remove(currRemoval)
        self.updateStatusReadout(None)
        # perform doMethodLater again after delay
        # This crashes when CTRL-C'ing, so this is a cheap hack.
        #return 2
        from direct.task import Task
        return Task.again

    def propMeetsReq(self, typeName, parentNP):
        if self.ui.parentToSelectedMenuItem.IsChecked():
            if base.direct.selected.last:
                parent = base.le.objectMgr.findObjectByNodePath(base.direct.selected.last)
                if parent:
                    parentNP[0] = parent[OG.OBJ_NP]
        else:
            parentNP[0] = None
        return True
